﻿using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using CSW.Framework.Common;

namespace CSW.Framework.Windows.ActiveDirectory
{
    /// <summary>
    /// SAM (Security Accounts Manager)
    /// </summary>
    public static class SAM
    {
        /// <summary>
        /// Gets the default naming context.
        /// </summary>
        /// <returns>The default naming context.</returns>
        public static string GetDefaultNamingContext()
        {
            DirectoryEntry root = new DirectoryEntry(@"LDAP://rootdse");
            return root.Invoke("GET", "defaultNamingContext").ToString();
        }

        /// <summary>
        /// Gets the user.
        /// </summary>
        /// <param name="organizationalUnit">The organizational unit name (example, "User Accounts").</param>
        /// <param name="userName">Login name of the user.</param>
        /// <returns>The user, if found; otherwise, <c>null</c>.</returns>
        public static User GetUser(string organizationalUnit, string userName)
        {
            string path = GetDefaultNamingContext();
            DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://OU={0},{1}", organizationalUnit, path), null, null, AuthenticationTypes.Secure);
            DirectorySearcher deSearch = new DirectorySearcher(de);
            deSearch.Filter = string.Format("(SAMAccountName={0})", userName);

            SearchResultCollection results = deSearch.FindAll();

            if (results.Count == 1)
            {
                SearchResult result = results[0];
                User user = new User
                                {
                                    LoginName = result.Properties["samaccountname"].OfType<string>().SingleOrDefault(),
                                    DisplayName = result.Properties["displayname"].OfType<string>().SingleOrDefault(),
                                    Mail = result.Properties["mail"].OfType<string>().SingleOrDefault(),
                                    GivenName = result.Properties["givenname"].OfType<string>().SingleOrDefault(),
                                    Surname = result.Properties["sn"].OfType<string>().SingleOrDefault(),
                                    MemberOfInternal = new List<string>(result.Properties["memberof"].OfType<string>().ToArray())
                                };

                return user;
            }

            return null;
        }

        /// <summary>
        /// Gets the user.
        /// </summary>
        /// <param name="organizationalUnit">The organizational unit name (example, "User Accounts").</param>
        /// <returns>The lists of users.</returns>
        public static List<User> GetAllUsers(string organizationalUnit)
        {
            string path = GetDefaultNamingContext();
            DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://OU={0},{1}", organizationalUnit, path), null, null, AuthenticationTypes.Secure);
            DirectorySearcher deSearch = new DirectorySearcher(de);

            SearchResultCollection results = deSearch.FindAll();

            List<User> users = new List<User>();
            foreach (SearchResult result in results)
            {
                string loginName = result.Properties["samaccountname"].OfType<string>().SingleOrDefault();
                if (string.IsNullOrEmpty(loginName))
                    continue;

                User user = new User
                                {
                                    LoginName = loginName,
                                    DisplayName = result.Properties["displayname"].OfType<string>().SingleOrDefault(),
                                    Mail = result.Properties["mail"].OfType<string>().SingleOrDefault(),
                                    GivenName = result.Properties["givenname"].OfType<string>().SingleOrDefault(),
                                    Surname = result.Properties["sn"].OfType<string>().SingleOrDefault(),
                                    MemberOfInternal = new List<string>(result.Properties["memberof"].OfType<string>().ToArray())
                                };

                users.Add(user);
            }

            users = users.OrderBy(p => p.LoginName.ToLower()).ToList();

            return users;
        }

        internal static void AddGroupsRecurisvely(ICollection<string> list)
        {
            AddGroupsRecurisvely(new List<string>(list), list);
        }

        private static void AddGroupsRecurisvely(IEnumerable<string> childList, ICollection<string> bigList)
        {
            Guard.ArgumentNotNull(childList, "childList");
            Guard.ArgumentNotNull(bigList, "bigList");

            foreach (string group in childList)
            {
                AddChildGroups(group, bigList);
            }
        }

        private static void AddChildGroups(string group, ICollection<string> bigList)
        {
            Guard.ArgumentNotNullOrEmptyString(group, "group");
            Guard.ArgumentNotNull(bigList, "bigList");

            DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", group), null, null, AuthenticationTypes.Secure);
            DirectorySearcher deSearch = new DirectorySearcher(de);

            List<string> childGroups = new List<string>();
            foreach (SearchResult result in deSearch.FindAll())
            {
                string objectCategory = result.Properties["objectcategory"].OfType<string>().SingleOrDefault();
                if (!objectCategory.Contains("CN=Group"))
                    break;

                IEnumerable<string> members = result.Properties["memberof"].OfType<string>();
                foreach (string member in members)
                {
                    de = new DirectoryEntry(string.Format("LDAP://{0}", member), null, null, AuthenticationTypes.Secure);
                    deSearch = new DirectorySearcher(de);

                    foreach (SearchResult childResult in deSearch.FindAll())
                    {
                        string childPath = childResult.Properties["adspath"].OfType<string>().SingleOrDefault();
                        childPath = childPath.Substring(7); // remove LDAP://

                        string childObjectCategory = childResult.Properties["objectcategory"].OfType<string>().SingleOrDefault();
                        if (!childObjectCategory.Contains("CN=Group"))
                            continue;

                        if (!string.IsNullOrEmpty(childPath))
                        {
                            if (!bigList.Contains(childPath))
                            {
                                childGroups.Add(childPath);
                                bigList.Add(childPath);
                            }
                        }
                    }
                }
            }

            AddGroupsRecurisvely(childGroups, bigList);
        }
    }
}